package cn.iocoder.yudao.framework.geo;

import cn.hutool.core.io.FileUtil;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import lombok.AllArgsConstructor;
import lombok.Data;
import org.locationtech.jts.geom.*;
import org.locationtech.jts.index.strtree.STRtree;

import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;

/**
 * <pre>
 * OOoO0OOoO0OOOooo0oOOOO0OOOOO0oooOO0ooOOO0Ooooo0OOOOo0ooooO0OOooo0Ooooo0OOOOO
 *  要在 Java 中使用 GeoJSON 和 JTS（Java Topology Suite）根据经纬度找到所在的区域，你需要以下几个步骤：
 *  1.解析 GeoJSON 文件：将 GeoJSON 文件解析为 JTS 几何对象。
 *  2.创建空间索引：为了高效地查询，可以使用空间索引。
 *  3.查询区域：根据给定的经纬度查找所在的区域。
 * OOoO0OOoO0OOOooo0oOOOO0OOOOO0oooOO0ooOOO0Ooooo0OOOOo0ooooO0OOooo0Ooooo0OOOOO
 * </pre>
 *
 * @author 山野羡民（1032694760@qq.com）
 * @since 2024/12/06
 */
public class GeoParser {
    private final STRtree spatialIndex = new STRtree();

    public void loadGeoJson(String geoJsonFilePath) {
        String geoJsonStr = FileUtil.readString(geoJsonFilePath, StandardCharsets.UTF_8);
        JSONObject jsonObject = JSONUtil.parseObj(geoJsonStr);
        if (!jsonObject.containsKey("features")) {
            throw new IllegalArgumentException("Invalid GeoJSON file: missing 'features' array.");
        }
        GeometryFactory geometryFactory = new GeometryFactory();
        JSONArray features = jsonObject.getJSONArray("features");
        features.stream().iterator().forEachRemaining(feature -> {
            if (feature instanceof JSONObject featureObject) {
                if (featureObject.containsKey("geometry") && featureObject.containsKey("properties")) {
                    JSONObject geometryObject = featureObject.getJSONObject("geometry");
                    String type = geometryObject.getStr("type");
                    Geometry geometry = null;
                    switch (type) {
                        case "Point":
                            JSONArray pointCoords = geometryObject.getJSONArray("coordinates");
                            double x = pointCoords.getDouble(0);
                            double y = pointCoords.getDouble(1);
                            geometry = geometryFactory.createPoint(new Coordinate(x, y));
                            break;
                        case "LineString":
                            JSONArray lineCoordsArray = geometryObject.getJSONArray("coordinates");
                            List<Coordinate> lineCoords = new ArrayList<>();
                            for (int i = 0; i < lineCoordsArray.size(); i++) {
                                JSONArray coord = lineCoordsArray.getJSONArray(i);
                                lineCoords.add(new Coordinate(coord.getDouble(0), coord.getDouble(1)));
                            }
                            geometry = geometryFactory.createLineString(lineCoords.toArray(new Coordinate[0]));
                            break;
                        case "Polygon":
                            JSONArray polygonRings = geometryObject.getJSONArray("coordinates");
                            List<Coordinate> polygonCoords = new ArrayList<>();
                            JSONArray linearRing = polygonRings.getJSONArray(0); // 只取外环
                            for (int i = 0; i < linearRing.size(); i++) {
                                JSONArray coord = linearRing.getJSONArray(i);
                                polygonCoords.add(new Coordinate(coord.getDouble(0), coord.getDouble(1)));
                            }
                            geometry = geometryFactory.createPolygon(polygonCoords.toArray(new Coordinate[0]));
                            break;
                        case "MultiPolygon":
                            JSONArray multiPolygons = geometryObject.getJSONArray("coordinates");
                            List<Polygon> polygons = new ArrayList<>();
                            for (int p = 0; p < multiPolygons.size(); p++) {
                                JSONArray polygonCoordinates = multiPolygons.getJSONArray(p);
                                JSONArray outerRing = polygonCoordinates.getJSONArray(0); // 外环
                                List<Coordinate> mpCoords = new ArrayList<>();
                                for (int i = 0; i < outerRing.size(); i++) {
                                    JSONArray coord = outerRing.getJSONArray(i);
                                    mpCoords.add(new Coordinate(coord.getDouble(0), coord.getDouble(1)));
                                }
                                Polygon polygon = geometryFactory.createPolygon(mpCoords.toArray(new Coordinate[0]));
                                polygons.add(polygon);
                            }
                            geometry = geometryFactory.createMultiPolygon(polygons.toArray(new Polygon[0]));
                            break;
                    }
                    if (geometry != null) {
                        JSONObject propertiesObject = featureObject.getJSONObject("properties");
                        String name = propertiesObject.getStr("name", "");
                        String adcode = propertiesObject.getStr("adcode", "");
                        spatialIndex.insert(geometry.getEnvelopeInternal(), new Region(adcode, name, geometry));
                    }
                }
            }
        });
    }

    public Region findRegion(double longitude, double latitude) throws IllegalArgumentException {
        Point point = new GeometryFactory().createPoint(new Coordinate(longitude, latitude));
        List<?> results = spatialIndex.query(point.getEnvelopeInternal());
        for (Object result : results) {
            Region region = (Region) result;
            if (region.getGeometry().contains(point)) {
                return region;
            }
        }
        throw new IllegalArgumentException("No region found for the given coordinates(" + longitude + "," + latitude + ").");
    }

    @AllArgsConstructor
    @Data
    public static class Region {
        private String adcode;
        private String name;
        private Geometry geometry;
    }

}
